#include <iostream>

#ifndef SPTR_HPP
#define SPTR_HPP

using namespace std;

namespace cs540{
template <typename T> class Sptr;

/* We will only create pointer of this class and using this pointer 
   we will keep a track of which type of object we get. This will help 
   in preventing from object slicing.
*/
class MyBase{
	
	public:
		virtual ~MyBase(){}
};

/* In this we keep U type data. In normal cases T & U type is same, but
   in case of inheritance both T & U will be different. 
*/   
template <typename U>
class MyDerived : public MyBase{
	public:
		MyDerived (){}
		MyDerived (U *o) : u_ptr(o){}
		U* u_ptr;
		~MyDerived(){
			delete u_ptr;
			u_ptr = NULL;
		}
};

/* This class keeps number of references */
class RefCount{
	
	private:
		int ref_count;
		pthread_mutex_t count_mutex;

	public:
		RefCount();
		virtual ~RefCount();
		int RefInc();
		int RefDec();
		int getCount(){return ref_count;}
};

template <typename T>
class Sptr{

	public:
		template <typename U>
		friend class Sptr;

		Sptr();						// Constructs a smart pointer
		virtual ~Sptr();
		template <typename U>				
		Sptr(U *);					// Constructs a smart pointer that points to the given object.
		Sptr(const Sptr &);				// Normal copy constructur
		template <typename U> 
		Sptr(const Sptr<U> &);				// Member templated copy constructur
		Sptr &operator=(const Sptr &);			// Normal assignment operator
		template <typename U> 
		Sptr<T> &operator=(const Sptr<U> &);		// Member templated assignment operator
		void reset();					// Set to point to the null pointer
		T &operator*() const;				// Return reference to the pointed-to object 
		T *operator->() const;				
		T *get() const;					// Return reference to the pointed-to object
		MyDerived<T> *get_ref() const;

		int SpRefInc() ;
		template <typename U>
		friend Sptr dynamic_pointer_cast(const Sptr<U> &sp);

		template <typename T1, typename T2>
		friend bool operator==(const Sptr<T1> &, const Sptr<T2> &);
	private:
		typedef void (Sptr::*unspecified_bool_type)() const; // typedef to a type that cannot be accessed by any external code
		void dummy_function() const {}			// 

	public:
		T *t_ref_ptr;
		MyBase *u_ref_ptr;
		RefCount *m_ref;
		operator unspecified_bool_type() const {	// Test the pointer for null in a bool context
		
			return t_ref_ptr != NULL ? &Sptr<T>::dummy_function : 0;
		}
};


/************************************ RefCount ************************************/
RefCount::RefCount(){
	ref_count = 0;
	pthread_mutex_init(&count_mutex, NULL);
}

RefCount::~RefCount(){
	pthread_mutex_destroy(&count_mutex);
}

int RefCount::RefInc(){
	
	pthread_mutex_lock (&count_mutex);
	int retval = ++ref_count;
	pthread_mutex_unlock (&count_mutex);
	
	return retval;
}

int RefCount::RefDec(){
	
	pthread_mutex_lock (&count_mutex);
	int retval = --ref_count;
	pthread_mutex_unlock (&count_mutex);

	return retval;
}

/************************************ Sptr ************************************/

template <typename T>
Sptr<T>::Sptr(){
	m_ref = NULL;
	t_ref_ptr = NULL;
	u_ref_ptr = NULL;
}

template <typename T>
Sptr<T>::~Sptr(){
	reset();
}

template <typename T>
int Sptr<T>::SpRefInc() {
	return this->m_ref->RefInc();
}

template <typename T>
template <typename U>
Sptr<T>::Sptr (U* obj){

	u_ref_ptr = NULL;
	t_ref_ptr = NULL;
	m_ref = NULL;
	if (obj){
		m_ref = new RefCount;
		u_ref_ptr = new MyDerived<U>(obj);
		t_ref_ptr = obj;
		m_ref->RefInc();
	}
}

template <typename T>
Sptr<T>::Sptr(const Sptr &sp){
	m_ref = sp.m_ref;
	u_ref_ptr = sp.u_ref_ptr;
	t_ref_ptr = sp.t_ref_ptr;

	if( m_ref)
		m_ref->RefInc();
}

template <typename T>
template <typename U>
Sptr<T>::Sptr (const Sptr<U> &obj){
	m_ref = obj.m_ref;
	u_ref_ptr = obj.u_ref_ptr;
	t_ref_ptr = obj.t_ref_ptr;
	if( m_ref)
		m_ref->RefInc();
}

template <typename T>
Sptr<T>& Sptr<T>::operator=(const Sptr<T> &obj){
	
	if (this == &obj)
		return *this;
	reset();
	m_ref = obj.m_ref;
	u_ref_ptr = obj.u_ref_ptr;
	t_ref_ptr = obj.t_ref_ptr;
	
	if( m_ref)
		m_ref->RefInc();
	return *this;
}

template <typename T>
template <typename U> 
Sptr<T> & Sptr<T>::operator=(const Sptr<U> &obj){

	if (u_ref_ptr == obj.u_ref_ptr)
		return *this;

	reset();
	m_ref = obj.m_ref;
	t_ref_ptr = obj.t_ref_ptr;
	u_ref_ptr = obj.u_ref_ptr;

	if( m_ref)
		m_ref->RefInc();

	return *this;
}

template <typename T>
void Sptr<T>::reset(){
	if (m_ref && (m_ref->RefDec() == 0)){
		delete u_ref_ptr;
		delete m_ref;
	}	
	m_ref = NULL;
	u_ref_ptr = NULL;
	t_ref_ptr = NULL;
}

template <typename T>
T & Sptr<T>::operator*() const{
	return *(t_ref_ptr);
}

template <typename T>
T * Sptr<T>::operator->() const{
	if (t_ref_ptr)
		return t_ref_ptr;
	else
		return NULL;
}

template <typename T>
T * Sptr<T>::get() const{
	if (t_ref_ptr)
		return t_ref_ptr;
	else
		return NULL;
}

/*
template <typename T>
T*  Sptr<T>::get_ref()const {
	return u_ref_ptr;
}*/

template <typename T, typename U>
Sptr<T> static_pointer_cast(const Sptr<U> &sp){
	Sptr<T> ptr ;
	ptr.m_ref = sp.m_ref;
	ptr.u_ref_ptr = sp.u_ref_ptr;

	ptr.t_ref_ptr = static_cast<T* >(sp.t_ref_ptr);
	ptr.m_ref->RefInc();
	return ptr;
}

template <typename T, typename U>
Sptr<T> dynamic_pointer_cast(const Sptr<U> &sp){
	
	Sptr<T> ptr ;
	ptr.m_ref = sp.m_ref;
	ptr.u_ref_ptr = sp.u_ref_ptr;

	ptr.t_ref_ptr = dynamic_cast<T* >(sp.t_ref_ptr);
	ptr.m_ref->RefInc();
	return ptr;
}

template <typename T1, typename T2>
bool operator==(const Sptr<T1> &sp1, const Sptr<T2> &sp2){

	if ( sp1.get() == sp2.get())
		return true;
	else
		return false;
}


}
#endif // SPTR_HPP
